package dao

import (
	"common/logs"
	"context"
	"core/models/db"
	"core/models/entity"
	"core/repo"
	"errors"
	"fmt"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"gorm.io/gorm"
	"hall/models/request"
	"time"
)

type GoodsDao struct {
	repo        *repo.Manager
	baseDao     *BaseDao
	activityDao *ActivityDao
}

func (d *GoodsDao) GoodsAll(ctx context.Context, req *request.GoodsIDAsPage) (int64, *[]db.GoodsHasOneType) {
	data := new([]db.GoodsHasOneType)
	DB := d.repo.DB.Client.WithContext(ctx).Model(&db.Goods{}).Where("type=? AND status=? ", req.Type, true)
	var count int64
	DB.Count(&count)
	DB.Preload("Types").
		Preload("Donate").
		Preload("Donate.LinkType").
		Preload("Donate.LinkRole").
		Preload("GoodsPay").
		Preload("GoodsPay.Types").
		Scopes(db.Paginate(req.Page, req.PageSize)).
		Find(data)
	return count, data
}

func (d *GoodsDao) MongoCreate(ctx context.Context, order *entity.GoodsOrder) (*entity.GoodsOrder, error) {
	table := d.repo.Mongo.Db.Collection("goods_order")
	_, err := table.InsertOne(ctx, order)
	if err != nil {
		return nil, err
	}
	return order, nil
}

func (d *GoodsDao) GetGoods(ctx context.Context, gid uint) *db.GoodsHasOneType {
	data := new(db.GoodsHasOneType)
	d.repo.DB.Client.WithContext(ctx).Model(&db.Goods{}).
		Where("id=? AND status=?", gid, true).
		Preload("Types").
		Preload("Donate").
		Preload("Donate.LinkType").
		Preload("Donate.LinkRole").
		Preload("GoodsPay").
		Preload("GoodsPay.Types").
		First(data)
	return data
}

func (d *GoodsDao) MongoGetOrder(ctx context.Context, cancel int, uid uint, gid uint) *entity.GoodsOrder {
	collection := d.repo.Mongo.Db.Collection("goods_order")
	singleResult := collection.FindOne(ctx, bson.D{
		{"uid", uid},
		{"gid", gid},
		{"payStatus", 0},
		{"createTime", bson.D{
			{"$lte", time.Now().Unix() + int64(cancel)},
		}},
	})
	order := new(entity.GoodsOrder)
	_ = singleResult.Decode(order)
	return order
}

func (d *GoodsDao) MongoOrderCancel(ctx context.Context, uid uint, code string) error {
	db := d.repo.Mongo.Db.Collection("goods_order")
	_, err := db.UpdateOne(ctx, bson.D{
		{"uid", uid},
		{"orderNo", code},
		{"payStatus", 0},
	}, bson.M{
		"$set": bson.M{
			"payStatus":  -1,
			"cancelTime": time.Now().Format(time.DateTime),
		},
	})
	return err
}

func (d *GoodsDao) GoodsNumSub(ctx context.Context, order *entity.GoodsOrder) (*entity.GoodsOrder, *db.GoodsHasOneType, error) {
	_db := d.repo.DB.Client.WithContext(ctx)
	tx := _db.Begin()
	_mdb := d.repo.Mongo
	mtx, _ := _mdb.Client.StartSession()
	_ = mtx.StartTransaction()
	defer mtx.EndSession(ctx)
	//1.查询商城数据
	goods := d.GetGoods(ctx, order.GID)
	if goods.Remain <= 0 {
		return nil, nil, errors.New("GoodsNumSub 商品数量不足")
	}
	//2.修改商城数据
	err := tx.Model(goods).
		Where("id=? AND status=?", order.GID, true).
		Update("remain", gorm.Expr("remain-?", 1)).Error
	if err != nil {
		tx.Rollback()
		_ = mtx.AbortTransaction(ctx)
		return nil, nil, err
	}
	//3.生成订单
	goodsOrder, err := d.MongoCreate(ctx, order)
	if err != nil {
		tx.Rollback()
		_ = mtx.AbortTransaction(ctx)
		return nil, nil, err
	}
	goods.Remain--
	_ = mtx.CommitTransaction(ctx)
	tx.Commit()
	return goodsOrder, goods, nil
}

func (d *GoodsDao) GoodsNumAdd(ctx context.Context, uid uint, code string) (*entity.GoodsOrder, *db.GoodsHasOneType, error) {
	_db := d.repo.DB.Client.WithContext(ctx)
	tx := _db.Begin()
	_mdb := d.repo.Mongo
	mtx, _ := _mdb.Client.StartSession()
	_ = mtx.StartTransaction()
	defer mtx.EndSession(ctx)
	//1.查找订单
	order := d.MongoGetOrderByOrderNo(ctx, uid, code)
	//2.查询商品数据
	goods := d.GetGoods(ctx, order.GID)
	//3.修改商城数据
	err := tx.Model(goods).
		Where("id=? AND status=?", order.GID, true).
		Update("remain", gorm.Expr("remain+?", 1)).Error
	if err != nil {
		tx.Rollback()
		_ = mtx.AbortTransaction(ctx)
		return nil, nil, err
	}
	//4.修改订单
	err = d.MongoOrderCancel(ctx, uid, code)
	if err != nil {
		tx.Rollback()
		_ = mtx.AbortTransaction(ctx)
		return nil, nil, err
	}
	goods.Remain++
	_ = mtx.CommitTransaction(ctx)
	tx.Commit()
	order.PayStatus = -1
	return order, goods, nil
}

func (d *GoodsDao) MongoGetOrderByOrderNo(ctx context.Context, uid uint, code string) *entity.GoodsOrder {
	collection := d.repo.Mongo.Db.Collection("goods_order")
	singleResult := collection.FindOne(ctx, bson.D{
		{"uid", uid},
		{"orderNo", code},
		{"payStatus", 0},
	})
	order := new(entity.GoodsOrder)
	_ = singleResult.Decode(order)
	return order
}

func (d *GoodsDao) OrderAll(ctx context.Context, req *request.GoodsIDAsPage, uid uint) (int64, *[]entity.GoodsOrder) {
	collection := d.repo.Mongo.Db.Collection("goods_order")
	whe := bson.D{
		{"uid", uid},
		{"goodsType", req.Type},
	}
	count, err := collection.CountDocuments(ctx, whe)
	if err != nil {
		return 0, nil
	}
	var page, pageSize = int64(req.Page), int64(req.PageSize)
	option := options.Find()
	option.SetSort(bson.D{{"createTime", -1}}) //降序
	option.SetSkip((page - 1) * pageSize)
	option.SetLimit(pageSize)
	cur, err := collection.Find(ctx, whe, option)
	if err != nil {
		return 0, nil
	}
	var results []entity.GoodsOrder
	for cur.Next(ctx) {
		var result entity.GoodsOrder
		err = cur.Decode(&result)
		if err == mongo.ErrNoDocuments {
			return 0, nil
		}
		results = append(results, result)
	}
	return count, &results
}

func (d *GoodsDao) OrderPay(ctx context.Context, order *entity.GoodsOrder, response string) error {
	_db := d.repo.DB.Client.WithContext(ctx)
	tx := _db.Begin()
	_mdb := d.repo.Mongo
	mtx, _ := _mdb.Client.StartSession()
	_ = mtx.StartTransaction()
	defer mtx.EndSession(ctx)
	//1.材料兑换的扣除材料
	var err error
	for _, v := range order.PayMethod {
		material := new(db.WarehouseMaterialHasOneType)
		_db.Model(material).Where("uid=? AND type_id=?", order.UID, v.TypeID).Preload("Types").First(material)
		if material.Num < v.Num {
			err = fmt.Errorf("[Goods]用户%d orderPay err:材料不足,res=%v", order.UID, material)
			break
		}
		err = tx.Model(material).Where("id=?", material.ID).Update("num", gorm.Expr("num-?", v.Num)).Error
		if err != nil {
			err = fmt.Errorf("[Goods]用户%d orderPay err:材料变更失败,res=%v,dbErr:%v", order.UID, material, err)
			break
		}
		materialLog := entity.MaterialLog{
			UID:        order.UID,
			WID:        material.ID,
			TypeID:     material.TypeID,
			TypeName:   material.Types.Name,
			TypeImg:    material.Types.Img,
			TypeModule: material.Types.Module,
			LinkID:     order.Id.String(),
			LinkTable:  "m_goods_order",
			Ago:        material.Num,
			Num:        -v.Num,
			After:      material.Num - v.Num,
			Remarks:    "商城-积分兑换",
			AddTime:    time.Now().Format(time.DateTime),
		}
		collection := _mdb.Db.Collection("material_log")
		_, err = collection.InsertOne(ctx, materialLog)
		if err != nil {
			err = fmt.Errorf("[Goods]用户%d orderPay err:材料变更记录添加失败,res=%v,dbErr:%v", order.UID, materialLog, err)
			break
		}
	}
	goodsOrder := _mdb.Db.Collection("goods_order")
	if err != nil {
		logs.Error("%v", err)
		tx.Rollback()
		_ = mtx.AbortTransaction(ctx)
		//回退订单
		go func() {
			_, _, err = d.GoodsNumAdd(ctx, order.UID, order.OrderNo)
			if err != nil {
				logs.Error("%v", err)
			}
		}()
		return err
	}
	//2.修改订单状态为已完成
	_, err = goodsOrder.UpdateOne(ctx, bson.D{
		{"_id", order.Id},
		{"payStatus", 0},
	}, bson.M{
		"$set": bson.M{
			"payStatus": 1,
			"payTime":   time.Now().Unix(),
			"payNotify": response,
		},
	})
	if err != nil {
		tx.Rollback()
		_ = mtx.AbortTransaction(ctx)
		return fmt.Errorf("修改订单数据失败")
	}
	//3.添加商城购买的商品
	switch order.TypeType {
	case 2: //材料
		material := new(db.WarehouseMaterial)
		materialType := new(db.WarehouseMaterialHasOneType)
		_db.Model(material).Where("uid=? AND type_id=?", order.UID, order.TypeID).Preload("Types").First(materialType)
		if materialType == nil || materialType.ID == 0 {
			err = _db.Create(&db.WarehouseMaterial{
				UID:    order.UID,
				TypeID: order.TypeID,
			}).Error
			if err != nil {
				err = fmt.Errorf("[Goods]用户%d orderPay err:添加材料失败,res=%v,dbErr:%v", order.UID, order, err)
				logs.Error("%v", err)
				tx.Rollback()
				_ = mtx.AbortTransaction(ctx)
				return err
			}
			_db.Model(material).Where("uid=? AND type_id=?", order.UID, order.TypeID).Preload("Types").First(materialType)
		}
		err = tx.Model(material).Where("id=?", materialType.ID).Update("num", gorm.Expr("num+?", 1)).Error
		if err != nil {
			err = fmt.Errorf("[Goods]用户%d orderPay err:材料变更失败,res=%v,dbErr:%v", order.UID, materialType, err)
			tx.Rollback()
			_ = mtx.AbortTransaction(ctx)
			logs.Error("%v", err)
			return err
		}
		materialLog := entity.MaterialLog{
			UID:        order.UID,
			WID:        materialType.ID,
			TypeID:     materialType.TypeID,
			TypeName:   materialType.Types.Name,
			TypeImg:    materialType.Types.Img,
			TypeModule: materialType.Types.Module,
			LinkID:     order.Id.String(),
			LinkTable:  "m_goods_order",
			Ago:        materialType.Num,
			Num:        1,
			After:      materialType.Num + 1,
			Remarks:    "商城-购买商品",
			AddTime:    time.Now().Format(time.DateTime),
		}
		collection := _mdb.Db.Collection("material_log")
		_, err = collection.InsertOne(ctx, materialLog)
		if err != nil {
			err = fmt.Errorf("[Goods]用户%d orderPay err:材料变更记录添加失败,res=%v,dbErr:%v", order.UID, materialLog, err)
			logs.Error("%v", err)
			tx.Rollback()
			_ = mtx.AbortTransaction(ctx)
			return err
		}
		break
	case 3: //装备
		typeEquip := new(db.TypeEquip)
		_db.Model(typeEquip).Where("type_id", order.TypeID).First(typeEquip)
		err = tx.Create(&db.WarehouseEquip{
			UID:    order.UID,
			TypeID: order.TypeID,
			SyNum:  typeEquip.Term,
		}).Error
		if err != nil {
			err = fmt.Errorf("[Goods]用户%d orderPay err:添加失败,res=%v,dbErr:%v", order.UID, order, err)
			logs.Error("%v", err)
			tx.Rollback()
			_ = mtx.AbortTransaction(ctx)
			return err
		}
		break
	}
	//4.官方发售的商品开启转赠功能
	if order.GoodsType == 1 {
		role := new(db.Role)
		err = tx.Model(role).Where("uid=?", order.UID).Update("is_transfer", true).Error
		if err != nil {
			tx.Rollback()
			_ = mtx.AbortTransaction(ctx)
			return fmt.Errorf("[userHandler]用户%d UserSign err:开启赠送失败 req=%v,dbErr=%v", order.UID, order, err)
		}
	}
	if order.GoodsType == 1 {
		//5.给用户增加购买卡包数量
		_, err = d.repo.Mongo.Db.Collection("user").UpdateOne(ctx, bson.D{
			{"uid", order.UID},
		}, bson.M{"$inc": bson.M{
			"buyCardNum": 1,
		}})
		if err != nil {
			tx.Rollback()
			_ = mtx.AbortTransaction(ctx)
			return fmt.Errorf("[userHandler]用户%d UserSign err:给用户增加购买卡包数量失败 req=%v,dbErr=%v", order.UID, order, err)
		}
		//6.给上级奖励
		//go d.baseDao.LastUserReward(order)
	}
	_ = mtx.CommitTransaction(ctx)
	tx.Commit()
	return nil
}

// OrderIosPay 苹果支付处理
func (d *GoodsDao) OrderIosPay(ctx context.Context, order *entity.GoodsOrder, response string) error {
	_db := d.repo.DB.Client.WithContext(ctx)
	tx := _db.Begin()
	_mdb := d.repo.Mongo
	mtx, _ := _mdb.Client.StartSession()
	_ = mtx.StartTransaction()
	defer mtx.EndSession(ctx)
	//1.修改订单状态为已完成
	var err error
	goodsOrder := _mdb.Db.Collection("goods_order")
	_, err = goodsOrder.UpdateOne(ctx, bson.D{
		{"_id", order.Id},
		{"payStatus", 0},
	}, bson.M{
		"$set": bson.M{
			"payStatus": 1,
			"payTime":   time.Now().Unix(),
			"payNotify": response,
		},
	})
	//2.添加商城购买的商品
	switch order.TypeType {
	case 2: //材料
		material := new(db.WarehouseMaterial)
		materialType := new(db.WarehouseMaterialHasOneType)
		_db.Model(material).Where("uid=? AND type_id=?", order.UID, order.TypeID).Preload("Types").First(materialType)
		if materialType == nil || materialType.ID == 0 {
			err = _db.Create(&db.WarehouseMaterial{
				UID:    order.UID,
				TypeID: order.TypeID,
			}).Error
			if err != nil {
				err = fmt.Errorf("[Goods]用户%d OrderIosPay err:添加材料失败,res=%v,dbErr:%v", order.UID, order, err)
				logs.Error("%v", err)
				tx.Rollback()
				_ = mtx.AbortTransaction(ctx)
				return err
			}
			_db.Model(material).Where("uid=? AND type_id=?", order.UID, order.TypeID).Preload("Types").First(materialType)
		}
		err = tx.Model(material).Where("id=?", materialType.ID).Update("num", gorm.Expr("num+?", 1)).Error
		if err != nil {
			err = fmt.Errorf("[Goods]用户%d OrderIosPay err:材料变更失败,res=%v,dbErr:%v", order.UID, materialType, err)
			tx.Rollback()
			_ = mtx.AbortTransaction(ctx)
			logs.Error("%v", err)
			return err
		}
		materialLog := entity.MaterialLog{
			UID:        order.UID,
			WID:        materialType.ID,
			TypeID:     materialType.TypeID,
			TypeName:   materialType.Types.Name,
			TypeImg:    materialType.Types.Img,
			TypeModule: materialType.Types.Module,
			LinkID:     order.Id.String(),
			LinkTable:  "ios_goods_order",
			Ago:        materialType.Num,
			Num:        1,
			After:      materialType.Num + 1,
			Remarks:    "商城-购买商品",
			AddTime:    time.Now().Format(time.DateTime),
		}
		collection := _mdb.Db.Collection("material_log")
		_, err = collection.InsertOne(ctx, materialLog)
		if err != nil {
			err = fmt.Errorf("[Goods]用户%d OrderIosPay err:材料变更记录添加失败,res=%v,dbErr:%v", order.UID, materialLog, err)
			logs.Error("%v", err)
			tx.Rollback()
			_ = mtx.AbortTransaction(ctx)
			return err
		}
		break
	case 3: //装备
		typeEquip := new(db.TypeEquip)
		_db.Model(typeEquip).Where("type_id", order.TypeID).First(typeEquip)
		err = tx.Create(&db.WarehouseEquip{
			UID:    order.UID,
			TypeID: order.TypeID,
			SyNum:  typeEquip.Term,
		}).Error
		if err != nil {
			err = fmt.Errorf("[Goods]用户%d OrderIosPay err:添加失败,res=%v,dbErr:%v", order.UID, order, err)
			logs.Error("%v", err)
			tx.Rollback()
			_ = mtx.AbortTransaction(ctx)
			return err
		}
		break
	}
	_ = mtx.CommitTransaction(ctx)
	tx.Commit()
	return nil
}

func (d *GoodsDao) OvertimeOrderList(ctx context.Context) *[]entity.GoodsOrder {
	collection := d.repo.Mongo.Db.Collection("goods_order")
	whe := bson.D{
		{"payStatus", 0},
	}
	cur, err := collection.Find(ctx, whe)
	if err != nil {
		return nil
	}
	var results []entity.GoodsOrder
	for cur.Next(ctx) {
		var result entity.GoodsOrder
		err = cur.Decode(&result)
		if err == mongo.ErrNoDocuments {
			return nil
		}
		results = append(results, result)
	}
	return &results
}

func (d *GoodsDao) BuyOrderCount(ctx context.Context, uid, gid uint, types int64) int64 {
	collection := d.repo.Mongo.Db.Collection("goods_order")
	whe := bson.D{
		{"uid", uid},
		{"gid", gid},
		{"payStatus", 1},
	}
	if types == 1 {
		t := time.Now()
		times := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.Local)
		whe = append(whe, bson.E{Key: "createTime", Value: bson.M{"$lte": times.Unix()}})
	}
	count, _ := collection.CountDocuments(ctx, whe)
	return count
}

func (d *GoodsDao) MongoNotify(ctx context.Context, response string) {
	collection := d.repo.Mongo.Db.Collection("goods_notify")
	collection.InsertOne(ctx, response)
}

func (d *GoodsDao) MongoUpOrderPayMethod(ctx context.Context, orderNo string, method string) error {
	collection := d.repo.Mongo.Db.Collection("goods_order")
	_, err := collection.UpdateOne(ctx, bson.D{
		{"orderNo", orderNo},
	}, bson.M{"$set": bson.M{"method": method}})
	return err
}

func NewGoodsDao(m *repo.Manager) *GoodsDao {
	return &GoodsDao{
		repo:        m,
		baseDao:     NewBaseDao(m),
		activityDao: NewActivityDao(m),
	}
}
